import * as fs from "fs";
import { getCurrentInstance, shallowRef, onBeforeMount } from "vue";

const isBrowser = () => typeof document !== "undefined";
export type packageOpt = {
  /** 文件夹名（默认：`dist`） */
  folder?: string;
  /** 是否返回已经转化好单位的包总大小（默认：`true`） */
  format?: boolean;
  /** 回调函数，返回包总大小（单位：字节） */
  callback: CallableFunction;
};
const formatSize = (sizeInBytes: number): string => {
  return `${sizeInBytes} bytes`;
};

export const getPackageSize = (options: packageOpt): void => {
  const { folder = "dist", callback, format = true } = options;
  const fileSizes: number[] = [];

  fs.readdir(folder, (err, files) => {
    if (err) {
      throw err;
    }

    let count = 0;

    const updateCount = (): void => {
      count++;
      if (count === files.length) {
        const totalSize = format
          ? formatSize(fileSizes.reduce((acc, size) => acc + size, 0))
          : fileSizes.reduce((acc, size) => acc + size, 0);
        callback(totalSize);
      }
    };

    files.forEach(file => {
      fs.stat(`${folder}/${file}`, async (err, stats) => {
        if (err) {
          throw err;
        }

        if (stats.isFile()) {
          fileSizes.push(stats.size);
          updateCount();
        } else if (stats.isDirectory()) {
          getPackageSize({
            folder: `${folder}/${file}/`,
            callback: updateCount
          });
        }
      });
    });

    if (files.length === 0) {
      callback(0);
    }
  });
};

// withInstall 方法，接收主组件和额外属性，并返回注册的组件
export const withInstall = (component, globalComponents = {}) => {
  if (component.install) {
    component.install = app => {
      for (const c of [component, ...Object.values(globalComponents)]) {
        app.component(c.name, c);
      }
    };
  }

  for (const [name, c] of Object.entries(globalComponents)) {
    component[name] = c;
  }

  return component;
};
export const cloneDeep = t => {
  if (t && typeof t === "object") {
    switch (Object.prototype.toString.call(t)) {
      case "[object Object]": {
        const clone = Object.create(t.__proto__);
        for (const key in t) {
          if (Object.prototype.hasOwnProperty.call(t, key)) {
            clone[key] = cloneDeep(t[key]);
          }
        }
        return clone;
      }
      case "[object Date]":
        return new Date(t.getTime());
      case "[object RegExp]":
        return new RegExp(t);
      case "[object Array]": {
        const clone = [];
        for (let i = 0; i < t.length; i++) {
          clone[i] = cloneDeep(t[i]);
        }
        return clone;
      }
      case "[object Set]": {
        const clone = new Set();
        t.forEach(item => {
          clone.add(cloneDeep(item));
        });
        return clone;
      }
      case "[object Map]": {
        const clone = new Map();
        t.forEach((value, key) => {
          clone.set(cloneDeep(key), cloneDeep(value));
        });
        return clone;
      }
    }
  }
  return t;
};
export const clone = (t, context) => {
  if (t) {
    switch (Object.prototype.toString.call(t)) {
      case "[object Object]": {
        const newObj = Object.create(t.__proto__);
        for (const prop in t) {
          if (Object.prototype.hasOwnProperty.call(t, prop)) {
            newObj[prop] = context ? clone(t[prop], context) : t[prop];
          }
        }
        return newObj;
      }
      case "[object Date]":
      case "[object RegExp]":
        return new t.constructor(t);
      case "[object Array]":
      case "[object Arguments]": {
        const newArray = [];
        for (let i = 0; i < t.length; i++) {
          newArray.push(context ? clone(t[i], context) : t[i]);
        }
        return newArray;
      }
      case "[object Set]": {
        const newSet = new Set();
        t.forEach(item => {
          newSet.add(context ? clone(item, context) : item);
        });
        return newSet;
      }
      case "[object Map]": {
        const newMap = new Map();
        t.forEach((value, key) => {
          newMap.set(
            context ? clone(key, context) : key,
            context ? clone(value, context) : value
          );
        });
        return newMap;
      }
      default:
        return t;
    }
  }
  return t;
};

export function isIterable(t) {
  return t && typeof t === "object" && typeof t[Symbol.iterator] === "function";
}

export function isAllEmpty(...args) {
  return args.every(arg => {
    return (
      Array.isArray(arg) ||
      (isIterable(arg) && arg.length === 0) ||
      ((arg instanceof Map || arg instanceof Set) && arg.size === 0) ||
      (arg !== null && typeof arg === "object" && Object.keys(arg).length === 0)
    );
  });
}

export const intersection = (...t) =>
  [...t].reduce((n, e) => n.filter(r => e.includes(r)));

class StorageMock {
  constructor() {
    this.data = {};
  }

  setItem(key, value) {
    this.data[key] = value;
  }

  getItem(key) {
    return this.data[key];
  }

  removeItem(key) {
    delete this.data[key];
  }

  clear() {
    this.data = {};
  }
}

class LocalStorage {
  constructor() {
    // 适配浏览器环境和非浏览器环境
    this.storage = this.isBrowser() ? window.localStorage : new StorageMock();
  }

  isBrowser() {
    return typeof window !== "undefined";
  }

  setItem(key, value) {
    if (!this.isEmpty(this.storage)) {
      this.storage.setItem(key, JSON.stringify(value));
    }
  }

  getItem(key) {
    if (!this.isEmpty(this.storage)) {
      return JSON.parse(this.storage.getItem(key));
    }
  }

  removeItem(key) {
    if (!this.isEmpty(this.storage)) {
      this.storage.removeItem(key);
    }
  }

  clear() {
    if (!this.isEmpty(this.storage)) {
      this.storage.clear();
    }
  }

  isEmpty(obj) {
    return this.isLengthZero(obj) || this.isType(obj, "Null");
  }

  isLengthZero(obj) {
    if (this.isType(obj, "Array") || this.isType(obj, "String")) {
      return obj.length === 0;
    } else if (this.isType(obj, "Map") || this.isType(obj, "Set")) {
      return obj.size === 0;
    } else if (this.isType(obj, "Object")) {
      return Object.keys(obj).length === 0;
    }
    return false;
  }

  isType(obj, type) {
    return Object.prototype.toString.call(obj) === `[object ${type}]`;
  }
}

export const storageLocal = () => new LocalStorage();

export const isIncludeAllChildren = (target, children) => {
  return target.every(item => {
    return children.some(child => child === item);
  });
};

export const isType = (t, n): boolean => {
  return Object.prototype.toString.call(t) === `[object ${n}]`;
};
export const isString = (t): boolean => {
  return isType(t, "String");
};
export const isArray = (t): boolean => {
  return isType(t, "Array");
};
export const isObject = t => {
  return t !== null && isType(t, "Object");
};
export const isNumber = t => {
  return isType(t, "Number");
};

function isEqualObject(t: any, n: any): boolean {
  if (t === n) {
    return true;
  }

  if (
    typeof t !== "object" ||
    typeof n !== "object" ||
    t === null ||
    n === null
  ) {
    return false;
  }

  const keysT = Object.keys(t);
  const keysN = Object.keys(n);

  if (keysT.length !== keysN.length) {
    return false;
  }

  for (const key of keysT) {
    if (!keysN.includes(key) || !isEqual(t[key], n[key])) {
      return false;
    }
  }

  return true;
}

function isEqualArray(t: any, n: any): boolean {
  if (!t || !n) {
    return false;
  }

  const length = t.length;

  if (length !== n.length) {
    return false;
  }

  for (let i = 0; i < length; i++) {
    if (!isEqual(t[i], n[i])) {
      return false;
    }
  }

  return true;
}
export function isEqual(t: any, n: any): boolean {
  const getType = (value: any): string => {
    return Object.prototype.toString.call(value);
  };

  if (getType(t) !== getType(n)) {
    return false;
  }

  if (getType(t) === "[object Object]") {
    return isEqualObject(t, n);
  }

  if (getType(t) === "[object Array]") {
    return isEqualArray(t, n);
  }

  if (getType(t) === "[object Function]") {
    return t === n ? true : t.toString() === n.toString();
  }

  return t === n;
}
export const isAsyncF = func => {
  // isType(func,'AsyncFunction')
  return func[Symbol.toStringTag] === "AsyncFunction";
};
export const isBoolean = t => {
  return isType(t, "Boolean");
};
export const isFunction = t => {
  return typeof t == "function";
};
export const isUrl = t => {
  const n =
    "^((https|http|ftp|rtsp|mms)?://)(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-z_!~*'()-]+.)*([0-9a-z][0-9a-z-]{0,61})?[0-9a-z].[a-z]{2,6})(:[0-9]{1,5})?((/?)|(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$";
  return new RegExp(n, "ig").test(t);
};
export const deviceDetection = (): boolean => {
  if (!isBrowser()) {
    return false;
  }
  const userAgent = navigator.userAgent.toLowerCase();

  const isMidp = userAgent.includes("midp");
  const isUcweb = userAgent.includes("ucweb");
  const isAndroid = userAgent.includes("android");
  const isIos = userAgent.includes("iphone os");
  const isWindowsCE = userAgent.includes("windows ce");
  const isRV1234 = userAgent.includes("rv:1.2.3.4");
  const isWindowsMobile = userAgent.includes("windows mobile");

  return (
    isMidp ||
    isUcweb ||
    isAndroid ||
    isIos ||
    isWindowsCE ||
    isRV1234 ||
    isWindowsMobile
  );
};

export const useGlobal = <T>() => {
  const instance = getCurrentInstance();
  const proxy = instance.proxy;
  // @ts-ignore
  const $config = proxy.$config,
    $echarts = proxy.$echarts,
    $storage = proxy.$storage;
  return {
    $config,
    $echarts,
    $storage
  } as T;
};

export const useDark = options => {
  const className = options?.className ?? "dark";
  const isDark = shallowRef(false);
  let observer;

  const updateDarkMode = () => {
    const targetElement = options?.selector
      ? options.selector === "html"
        ? document.documentElement
        : document.body
      : document.documentElement;
    isDark.value = targetElement.classList.contains(className);
  };

  const toggleDarkMode = () => {
    const targetElement = options?.selector
      ? options.selector === "html"
        ? document.documentElement
        : document.body
      : document.documentElement;

    targetElement.classList.toggle(className, !isDark.value);
  };

  onBeforeMount(() => {
    const targetElement = options?.selector
      ? options.selector === "html"
        ? document.documentElement
        : document.body
      : document.documentElement;

    updateDarkMode();

    observer = new MutationObserver(updateDarkMode);
    observer.observe(targetElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
  });

  return {
    isDark,
    toggleDark: toggleDarkMode
  };
};
export const openLink = (url, target = "_blank") => {
  // 检查是否支持 document 对象
  const c = typeof document < "u";
  if (!c) {
    return; // 如果不支持，则直接返回，不执行后续代码
  }

  // 创建一个链接元素
  const link = document.createElement("a");
  // 设置链接的地址和目标窗口
  link.setAttribute("href", url);
  link.setAttribute("target", target);
  // 添加安全属性
  link.setAttribute("rel", "noreferrer noopener");
  // 设置一个唯一的标识符作为链接的 id
  link.setAttribute("id", "external");

  // 检查是否已经存在同样 id 的链接元素
  const existingLink = document.getElementById("external");
  // 如果存在，则先将其从文档中移除
  if (existingLink) {
    document.body.removeChild(existingLink);
  }
  // 将新创建的链接元素添加到页面的 body 中
  document.body.appendChild(link);
  // 模拟用户点击该链接
  link.click();
  // 点击后，将链接元素从文档中移除
  link.remove();
};
/**
 * 防抖函数
 * @param func
 * @param delay
 * @param immediate
 */
export const debounce = (func, delay = 200, immediate = false) => {
  let timeoutId, context;

  return function () {
    clearTimeout(timeoutId);
    if (immediate) {
      /* eslint-disable prefer-rest-params */
      timeoutId || func.call(context, ...arguments);
      timeoutId = setTimeout(() => (timeoutId = null), delay);
    } else {
      timeoutId = setTimeout(() => func.call(context, ...arguments), delay);
    }
  };
};

export const throttle = (func, delay = 1000) => {
  let timer;
  let lastExecutedTime = 0;

  return function () {
    const currentTime = Date.now();

    if (!timer) {
      func.apply(this, arguments);
      lastExecutedTime = currentTime;
      timer = setTimeout(() => {
        timer = null;
        const timeSinceLastExecution = currentTime - lastExecutedTime;
        if (timeSinceLastExecution >= delay) {
          func.apply(this, arguments);
          lastExecutedTime = currentTime;
        }
      }, delay);
    }
  };
};

/**
 *
 * @param toggle 是否切换样式
 * @param className 要切换的样式名
 * @param element 要切换样式的元素
 */
export const toggleClass = (toggle, className, element) => {
  if (!toggle) return;

  const targetElement = element || document.body;
  const currentClassName = targetElement.className;
  const updatedClassName = currentClassName
    .replace(className, "")
    .trim()
    .split(/\s+/)
    .join(" ");

  targetElement.className = toggle
    ? `${updatedClassName} ${className}`
    : updatedClassName;
};
export const addClass = (element, className, additionalClass) => {
  if (isBrowser()) {
    if (element && !hasClass(element, className)) {
      element.className += " " + className;
    }
    if (additionalClass && !hasClass(element, additionalClass)) {
      element.className += " " + additionalClass;
    }
  }
};
export const removeClass = (element, className, additionalClass) => {
  if (isBrowser()) {
    if (hasClass(element, className)) {
      const regex = new RegExp("(\\s|^)" + className + "(\\s|$)");
      element.className = element.className.replace(regex, " ").trim();
    }
    if (additionalClass && hasClass(element, additionalClass)) {
      const regex = new RegExp("(\\s|^)" + additionalClass + "(\\s|$)");
      element.className = element.className.replace(regex, " ").trim();
    }
  }
};
/**
 * @param toggle 是否切换样式
 * @param className 要检查的样式名
 */
export const hasClass = (toggle, className) => {
  if (!isBrowser()) return false;

  if (toggle) {
    return !!toggle.className.match(
      new RegExp("(\\s|^)" + className + "(\\s|$)")
    );
  } else {
    return false;
  }
};
/**
 * 生成隨機顏色
 * @param options
 */
export const randomColor = options => {
  const { type = "rgb", num = 0 } = options;

  if (num === 0) {
    switch (type) {
      case "rgb":
        return crypto ? getRandomRgbColor() : undefined;
      case "hex":
        return generateHexColor();
      case "hsl":
        return generateHslColor();
    }
  } else {
    const rgbColors = [],
      hexColors = [],
      hslColors = [];
    switch (type) {
      case "rgb":
        if (!crypto) return;
        for (let i = 0; i < num; i++) {
          rgbColors.push(getRandomRgbColor());
        }
        return rgbColors;
      case "hex":
        for (let i = 0; i < num; i++) {
          hexColors.push(generateHexColor());
        }
        return hexColors;
      case "hsl":
        for (let i = 0; i < num; i++) {
          hslColors.push(generateHslColor());
        }
        return hslColors;
    }
  }
};

const getRandomRgbColor = () => {
  return crypto.getRandomValues(new Uint8Array(3)).toString();
};

const generateHexColor = () => {
  return `#${Math.floor(Math.random() * 16777215)
    .toString(16)
    .padStart(6, `${Math.random() * 10}`)}`;
};

const generateHslColor = () => {
  return [
    360 * Math.random(),
    `${100 * Math.random()}%`,
    `${100 * Math.random()}%`
  ].toString();
};

// 生成随机整数的函数
function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

// 生成 HSL 颜色字符串的函数
function toHSL(hue, saturation, lightness) {
  return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
}

// 主函数，生成线性渐变
export const randomGradient = (options = {}) => {
  // baseHue (t)：基础色调，随机默认值在 0 到 360 之间。
  // hueOffset (n)：色调偏移量，默认为 30。
  // saturation (r)：饱和度，默认为 70%。
  // lightness (o)：亮度，默认为 60%。
  // angle (s)：渐变角度，默认为 135 度。
  // randomizeHue (i)：是否随机化色调，默认为 false。
  // randomizeSaturation (u)：是否随机化饱和度，默认为 false。
  // randomizeLightness (c)：是否随机化亮度，默认为 false。
  // randomizeAngle (f)：是否随机化角度，默认为 false。
  const {
    baseHue = randomInt(0, 360),
    hueOffset = 30,
    saturation = 70,
    lightness = 60,
    angle = 135,
    randomizeHue = false,
    randomizeSaturation = false,
    randomizeLightness = false,
    randomizeAngle = false
  } = options;

  const hue = randomizeHue ? randomInt(0, 360) : baseHue;
  const sat = randomizeSaturation ? randomInt(50, 100) : saturation;
  const light = randomizeLightness ? randomInt(40, 70) : lightness;
  const gradientAngle = randomizeAngle ? randomInt(0, 360) : angle;

  const color1 = toHSL(hue, sat, light);
  const color2 = toHSL((hue + hueOffset) % 360, sat, light);
  const color3 = toHSL((hue + 180) % 360, sat, light);

  return `linear-gradient(${gradientAngle}deg, ${color1}, ${color2}, ${color3})`;
};

/**
 * @description 延迟函数
 * @param timeout 延迟时间（毫秒），默认 `20`
 * @returns Promise
 */
export const delay = (timeout = 200): Promise<unknown> => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
};

export const getKeyList = (t, n) => {
  const result = [];
  for (const r of t) {
    if (r[n]) {
      result.push(r[n]);
    }
  }
  return Array.from(new Set(result));
};

/**
 * 下载base 64文件
 * @param data
 * @param filename
 * @param type
 * @param target
 */
export const downloadByBase64 = (data, filename, type, target) => {
  const blobData = convertToBlob(data);
  downloadData(blobData, filename, type, target);
};
const convertToBlob = data => {
  if (!isBrowser()) return;

  const parts = data.split(",");
  const fileType = parts[0].match(/:(.*?);/)[1];
  const encodedData = window.atob(parts[1]);
  const dataSize = encodedData.length;
  const uint8Array = new Uint8Array(dataSize);

  for (let i = 0; i < dataSize; i++) {
    uint8Array[i] = encodedData.charCodeAt(i);
  }

  return new Blob([uint8Array], { type: fileType });
};
const downloadData = (data, filename, type, target) => {
  // 检查是否在浏览器环境中
  if (!isBrowser()) return;

  // 创建 Blob 对象
  let blobData = data;
  if (typeof target !== "undefined" && typeof target !== "object") {
    blobData = [target, data];
  }
  const blob = new Blob(blobData, { type: type || "application/octet-stream" });

  // 创建下载链接
  const url = window.URL.createObjectURL(blob);

  // 创建下载链接的 <a> 元素
  const link = document.createElement("a");
  link.style.display = "none";
  link.href = url;
  link.setAttribute("download", filename);

  // 设置在新标签页打开下载链接（如果支持）
  if (typeof link.download !== "undefined") {
    link.setAttribute("target", "_blank");
  }

  // 将 <a> 元素添加到页面并触发下载
  document.body.appendChild(link);
  link.click();

  // 清理并释放资源
  document.body.removeChild(link);
  window.URL.revokeObjectURL(url);
};
/**
 * 截取字符串
 * @param t ：string
 * @param n :string
 */
export const subBefore = (t, n) => {
  return isString(n) ? t.substring(0, t.indexOf(n)) : "";
};
export const subAfter = (t, n) => {
  return isString(n) ? t.substring(t.lastIndexOf(n) + n.length, t.length) : "";
};
/**
 * 将给定的文本 text 中指定位置indices的字符替换为占位符
 * @param text
 * @param indices
 * @param placeholder
 */
export const hideTextAtIndex = (text, indices, placeholder = "*") => {
  const isRangeObject = value =>
    isObject(value) && "start" in value && "end" in value;
  const isEmptyRange = range => range.start === range.end;
  const isValidRange = (start, end) => start >= 0 && start < end;

  if (isString(text)) {
    text = text.toString();
  }
  if (!isArray(indices)) {
    indices = Array.of(indices);
  }

  const characters = text.split("");
  for (let i = 0; i < indices.length; i++) {
    const index = indices[i];
    if (isRangeObject(index) && !isEmptyRange(index)) {
      const { start, end } = index;
      if (isValidRange(start, end)) {
        characters.fill(placeholder, start, end + 1);
      }
      continue;
    }
    if (isString(index) && Number.isInteger(index) && index >= 0) {
      characters[indices[i]] = placeholder;
    }
  }
  return characters.join("");
};

export const isEmpty = value => {
  if (
    (Array.isArray(value) || isString(value) || !value) &&
    value.length === 0
  ) {
    return true;
  }
  if ((value instanceof Map || value instanceof Set) && value.size === 0) {
    return true;
  }
  return isObject(value) && Object.keys(value).length === 0;
};

export function getTime(seconds, padZero = true) {
  const padWithZero = number => {
    if (number < 10) {
      return "0" + number;
    }
    return number.toString();
  };
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  const remainingSeconds = Math.floor(seconds % 60);

  const formattedHours = padZero ? padWithZero(hours) : hours;
  const formattedMinutes = padZero ? padWithZero(minutes) : minutes;
  const formattedSeconds = padWithZero(remainingSeconds);

  return {
    h: formattedHours,
    m: formattedMinutes,
    s: formattedSeconds
  };
}

export function copyTextToClipboard(text) {
  const target = document.body;

  // 创建临时 textarea 元素
  const textarea = document.createElement("textarea");

  // 只设置必要样式
  textarea.setAttribute("style", "position: fixed; top: -9999px;");

  // 设置内容并选择
  textarea.value = text;
  textarea.focus();
  textarea.select();

  // 将 textarea 插入至 body
  target.appendChild(textarea);

  // 尝试复制
  // 尝试复制
  let success;
  /* eslint-disable no-useless-catch */
  try {
    success = document.execCommand("copy");
  } catch (err) {
    throw err;
  }

  // 清除 textarea
  target.removeChild(textarea);

  return success;
}

export function isPhone(t) {
  return /^[1](([3][0-9])|([4][0,1,4-9])|([5][0-3,5-9])|([6][2,5,6,7])|([7][0-8])|([8][0-9])|([9][0-3,5-9]))[0-9]{8}$/.test(
    t
  );
}

export function isEmail(t) {
  return /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(t);
}

export function isQQ(t) {
  return /^[1-9][0-9]{4,12}$/.test(t.toString());
}

export function isPostCode(t) {
  return /^[1-9][0-9]{5}$/.test(t.toString());
}

export function formatBytes(size, decimalPlaces = 2) {
  if (size === 0) {
    return "0 Bytes";
  }

  const base = 1024;
  const units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];

  const exponent = Math.floor(Math.log(size) / Math.log(base));
  const formattedSize = parseFloat(
    (size / Math.pow(base, exponent)).toFixed(decimalPlaces)
  );

  return `${formattedSize} ${units[exponent]}`;
}

export function getQueryMap(url) {
  if (!isUrl(url)) {
    console.error(`${url}不符合超链接规范`);
    return {};
  }

  const queryString = url.slice(url.indexOf("?") + 1);
  const queryParams = queryString.split("&");
  const queryMap = {};

  for (let i = 0; i < queryParams.length; i++) {
    const [key, value] = queryParams[i].split("=");
    queryMap[key] = value;
  }
  return queryMap;
}
